﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;

using JigLibSDX;
using JigLibSDX.Collision;
using JigLibSDX.Geometry;
using JigLibSDX.Math;
using JigLibSDX.Physics;
using JigLibSDX.Utils;

using SDX = SlimDX;
using SD9 = SlimDX.Direct3D9;

using DemoFramework;

namespace Demo_1
{
    public partial class Demo : Form
    {
        #region Non-Physics Code: Attributes
        //directx
        private SD9.Device _graphigsDevice;
        private SD9.Direct3D _direct3D;

        //screen size
        private const int _WIDTH = 640;
        private const int _HEIGHT = 480;

        //cam
        private Camera _camera;

        //input
        private BasicInput _input;

        //timing
        private Stopwatch _stopwatch;
        private float _secondsPerFrame = 0.016666f;
        #endregion

        private PhysicsSystem _physics;

        private SD9.Mesh _mesh;
        private CollisionSkin _skin;
        private Body _body;
        private SDX.Vector3 _centerOfMass;

        #region Non-Physics Code: Basic Init, Update & Terminate Handling
        #region Init
        public Demo()
        {
            InitializeComponent();
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
            this.Width = _WIDTH;
            this.Height = _HEIGHT;

            _camera = new Camera(new SDX.Vector3(0f, 2f, -10f), SDX.Vector3.Zero, SDX.Vector3.UnitY, _WIDTH, _HEIGHT, (float)Math.PI / 2f, 0.001f, 100f);

            InitializeGraphics();
            InitializeLight();
            InitializePhysics();
            InitializePhysicsObjects();

            LoadContent();

            _input = new BasicInput(this.Handle);
            _stopwatch = new Stopwatch();
        }
        #endregion

        #region Form Closing
        private void Demo_FormClosing(object sender, FormClosingEventArgs e)
        {
            UnloadContent();
        }
        #endregion

        #region Update & Draw Controller
        private void Demo_Paint(object sender, PaintEventArgs e)
        {
            if (_stopwatch.IsRunning)
            {
                float elapsedSeconds = _stopwatch.ElapsedMilliseconds * 0.001f;

                if (elapsedSeconds >= _secondsPerFrame)
                {
                    _stopwatch.Reset();
                    _stopwatch.Start();

                    Update(elapsedSeconds);
                    Draw(elapsedSeconds);
                }
            }
            else
            {
                _stopwatch.Start();
            }

            this.Invalidate();
        }
        #endregion

        #region Init Graphics
        private void InitializeGraphics()
        {
            SD9.PresentParameters presentParams = new SD9.PresentParameters();
            presentParams.BackBufferWidth = _WIDTH;
            presentParams.BackBufferHeight = _HEIGHT;
            presentParams.BackBufferFormat = SD9.Format.A8R8G8B8;
            presentParams.DeviceWindowHandle = this.Handle;

            _direct3D = new SD9.Direct3D();
            _graphigsDevice = new SD9.Device(_direct3D, 0, SD9.DeviceType.Hardware, this.Handle, SD9.CreateFlags.HardwareVertexProcessing, presentParams);

            _graphigsDevice.SetRenderState(SD9.RenderState.AlphaBlendEnable, true);
            _graphigsDevice.SetRenderState(SD9.RenderState.SourceBlend, SD9.Blend.SourceAlpha);
            _graphigsDevice.SetRenderState(SD9.RenderState.DestinationBlend, SD9.Blend.BothInverseSourceAlpha);
            _graphigsDevice.SetRenderState(SD9.RenderState.Lighting, true);
            _graphigsDevice.SetRenderState(SD9.RenderState.CullMode, SD9.Cull.None);
            _graphigsDevice.SetRenderState(SD9.RenderState.FillMode, SD9.FillMode.Solid);
            _graphigsDevice.SetRenderState(SD9.RenderState.NormalizeNormals, true);
        }
        #endregion

        #region Init Lights
        private void InitializeLight()
        {
            //light 1
            SDX.Vector3 direction = new SDX.Vector3(-1f, 0f, -1f);
            direction.Normalize();

            SD9.Light light = new SD9.Light();
            light.Type = SD9.LightType.Directional;
            light.Direction = direction;
            light.Diffuse = Color.White;
            light.Ambient = Color.White;

            _graphigsDevice.SetLight(0, light);
            _graphigsDevice.EnableLight(0, true);

            //light 2
            direction = new SDX.Vector3(1f, 0f, 1f);
            direction.Normalize();

            light = new SD9.Light();
            light.Type = SD9.LightType.Directional;
            light.Direction = direction;
            light.Diffuse = Color.White;
            light.Ambient = Color.White;

            _graphigsDevice.SetLight(1, light);
            _graphigsDevice.EnableLight(1, true);
        }
        #endregion
        #endregion

        private void InitializePhysics()
        {
            _physics = new PhysicsSystem();

            _physics.CollisionSystem = new CollisionSystemSAP();
            _physics.CollisionSystem.UseSweepTests = true;

            /// I'm using PhysicsSystem.Solver.Accumulated because it's the best looking solver.
            /// This might be slow if you tend to have worlds with many complex objects.
            _physics.SolverType = PhysicsSystem.Solver.Accumulated;

            /// Improves speed and prevents jiggling.
            _physics.EnableFreezing = true;

            _physics.NumCollisionIterations = 10;
            _physics.NumContactIterations = 10;
            _physics.NumPenetrationRelaxtionTimesteps = 15;

            //Set Gravity
            _physics.Gravity = new SDX.Vector3(0f, -10f, 0f);
        }

        private void InitializePhysicsObjects()
        {
            GetPhysicsInformation(out _body, out _skin, out _centerOfMass);
            _body.Position = new SDX.Vector3(0f, 5f, 0f);
        }

        #region Non-Physics Code: Load & Unload Content
        private void LoadContent()
        {
            _mesh = SD9.Mesh.FromFile(_graphigsDevice, @"Content\Meshes\sphere.x", SD9.MeshFlags.Managed);
            _mesh.OptimizeInPlace(SD9.MeshOptimizeFlags.VertexCache | SD9.MeshOptimizeFlags.Compact | SD9.MeshOptimizeFlags.AttributeSort);
        }

        private void UnloadContent()
        {
            _mesh = null;
        }
        #endregion

        private void Update(float elapsedSeconds)
        {
            #region Non-Physics Code: Input Update
            _input.Update();
            #endregion

            //Apply forces
            // ...

            //Update Physics
            // Use stable steps (small steps are good - big steps are bad).
            // noone (JigLibX author) suggests you use a maximum step of 1/60.
            // 1/50 worked well for me, so I guess it's ok to use 1/50 :).
            if (elapsedSeconds < 0.02f)
            {
                _physics.Update(elapsedSeconds);
            }
            else
            {
                /// Ok, your game is running slowly. There are still some limits in computing.
                /// Use less complexity, objects or tell the user to lower the rendering settings.
                _physics.Update(0.02f);
            }


            #region Non-Physics: Note
            //Update Positions
            // ... for example: Camera or graphics positions that depend on objects from the physics world.
            #endregion
        }

        #region Non-Physics Code: Draw
        private void Draw(float elapsedSeconds)
        {
            _graphigsDevice.Clear(SD9.ClearFlags.Target | SD9.ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0);
            _graphigsDevice.BeginScene();

            _graphigsDevice.SetTransform(SD9.TransformState.View, _camera.View);
            _graphigsDevice.SetTransform(SD9.TransformState.Projection, _camera.Projection);

            _graphigsDevice.SetTransform(SD9.TransformState.World, SDX.Matrix.Translation(-_centerOfMass) * _body.Orientation * SDX.Matrix.Translation(_body.Position));

            SD9.ExtendedMaterial[] extMat = _mesh.GetMaterials();
            _graphigsDevice.Material = extMat[0].MaterialD3D;
            _mesh.DrawSubset(0);

            _graphigsDevice.EndScene();
            _graphigsDevice.Present();
        }
        #endregion


        // The following function was generated with the CollisionSkin Editor, which is part of JigLibSDX.

        /// <summary>
        /// Creates all needed object information for the JigLibSDX physics simulator.
        /// </summary>
        /// <param name="body">Returns Body.</param>
        /// <param name="skin">Returns CollisionSkin.</param>
        /// <param name="centerOfMass">Returns the center of mass wich is usefull for drawing objects.</param>
        private void GetPhysicsInformation(out JigLibSDX.Physics.Body body, out JigLibSDX.Collision.CollisionSkin skin, out SDX.Vector3 centerOfMass)
        {
            #region Header
            // Variables
            JigLibSDX.Collision.MaterialProperties materialProperties;
            JigLibSDX.Geometry.PrimitiveProperties primitiveProperties;
            JigLibSDX.Geometry.Sphere sphere;

            #region Mass Variables
            float mass;
            SDX.Matrix inertiaTensor;
            SDX.Matrix inertiaTensorCoM;
            #endregion

            // Create Skin & Body
            body = new JigLibSDX.Physics.Body();
            skin = new JigLibSDX.Collision.CollisionSkin(body);
            body.CollisionSkin = skin;
            #endregion

            #region Primitive Properties
            primitiveProperties = new JigLibSDX.Geometry.PrimitiveProperties();
            primitiveProperties.MassDistribution = JigLibSDX.Geometry.PrimitiveProperties.MassDistributionEnum.Solid;
            primitiveProperties.MassType = JigLibSDX.Geometry.PrimitiveProperties.MassTypeEnum.Mass;
            primitiveProperties.MassOrDensity = 0f;
            #endregion

            #region Primitive 0
            // MaterialProperties:
            materialProperties = new JigLibSDX.Collision.MaterialProperties();
            materialProperties.StaticRoughness = 0.5f;
            materialProperties.DynamicRoughness = 0.35f;
            materialProperties.Elasticity = 0.25f;

            // Primitive:
            sphere = new JigLibSDX.Geometry.Sphere(new SDX.Vector3(0f, 0f, 0f), 1f);
            skin.AddPrimitive(sphere, materialProperties);
            #endregion

            #region Footer
            // Extract Mass Properties
            skin.GetMassProperties(primitiveProperties, out mass, out centerOfMass, out inertiaTensor, out inertiaTensorCoM);

            // Set Mass Properties
            body.BodyInertia = inertiaTensorCoM;
            body.Mass = mass;

            // Sync Body & Skin
            body.MoveTo(SDX.Vector3.Zero, SDX.Matrix.Identity);
            skin.ApplyLocalTransform(new JigLibSDX.Math.Transform(-centerOfMass, SDX.Matrix.Identity));

            // Enable Body
            body.EnableBody();
            #endregion
        }
    }
}
